//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   PACKAGE : dcf
//   FILE    : Component.java
//   CLASS   : Component
//   DATE    : 2006-04-01
//   
//   DESCRIPTION
//   The Component class is a container that can send and receive messages. The behavior of a component is 
//   determined the behavior it is provided at initialization. The component executes on its own threads.
//   
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   Copyright (c) 2006 by Hal Smith
//
//   Permission is hereby granted, free of charge, to any person obtaining a copy of this source code 
//   (the "Code")to deal with the Code without restriction, including without limitation the rights to use, copy,
//   modify, merge, publish and distribute the Code, and to permit persons to whom the Code is furnished to do 
//   so, subject to the following conditions:
//
//   The above copyright notice and this permission notice shall be included in all copies or substantial 
//   portions of the Software.
//   
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//   VERSION HISTORY
//
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

package dcf;

import java.io.*;
import java.net.*;
import java.util.concurrent.*;

public class Component {
	
	// The endpoint at which the component is listening.
	//
	private Endpoint     localEndpoint;	
	private ServerSocket serverSocket;

	// 
	private Behavior          behavior;
	private Thread		      portListenerThread;
	private Thread            outgoingListenerThread;
	private IOutgoingBehavior outgoingBehavior;
	
	private LinkedBlockingQueue<Envelope> outgoingMessages;

	
	// The PortListener local class defines a threadable operation for listening for incoming messages.
	//
	private class PortListener implements Runnable {

		// This implements the run() operation defined on Runnable. The operation listens to the server socket
		// waiting for a message to arrive. If a known message arrives, it is forwarded to the to the component
		// for processing. If the message is not known, an error is logged and the message ignored. There are 
		// no exceptions thrown.
		//
		public void run() {
			
			while(true) {

				try	{
					
					// Attach to the server socket and wait for a message to arrive. When a message arrives, 
					// verify that it is an "envelope" and forward it to the component for routing. If the 
					// message is not an "envelope", log an error and wait for the next message.
					//
					Socket            socket            = serverSocket.accept();
					InputStream       inputStream       = socket.getInputStream();
					ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
					Object            object            = objectInputStream.readObject();

					if (object instanceof Envelope) {
							routeMessage((Envelope)object);
							System.out.println(getClass().getName() + ".run():");
							System.out.println("object client found : host "+((Envelope)object).getHost()+" port "+((Envelope)object).getPort()+" message "+((Envelope)object).getMessage());
					
					} else {
						System.out.println(getClass().getName() + ".run():");
						System.out.println("   Message received is not an envelope!");
						System.out.println("   Object:" + object.toString());
					}
					

				} catch(Exception e) {
					
					// If an exception occurs, log an error and return to waiting for a message. 
					//
					// TODO: This error handling needs to be refined. Certain errors will signal a
					//       problem that cannot be rectified. We don't want to continuously loop
					//       generating the same error. We need to shutdown gracefully.
					//
					System.out.println("Error of client Component");
					System.out.println(getClass().getName() + ".run():");
					System.out.println("   Unexpected exception: " + e.toString());
				
				}
				
			}
		
		}
		
	}

	
	// The OutgoingBehavior implements the IOutgoingBehavior interface which provides operations relating
	// to messages being sent from the component.
	//
	private class OutgoingBehavior implements IOutgoingBehavior {
		
		// This operation wraps up the message and places it in the outgoing message queue.  If the message
		// is successfully placed in the queue, the operation returns true. If any error occurs, the operation
		// returns false and an error is logged. There are no exceptions thrown by this operation.
		//
		// NOTE: If this operation returns true it does not mean the message was successfully received by the 
		//       remomte endpoint. It only means that the operation was able to get the message on its way. 
		//       Errors that occur later in the delivery process will appear in the error log.
		//
		public boolean sendTo(Endpoint endpoint, Message message) {
			
			boolean result = true;
			
			try {
				
				Envelope e = new Envelope(endpoint, message);
	
				outgoingMessages.put(e);
	
			} catch (Exception e) {
				
				// If an exception occurs, log it and continue.
				//
				System.out.println(getClass().getName() + ".sendTo(Endpoint, Message):");
				System.out.println("   Exception occurred: " + e.toString());
				
				result = false;
				
			} // try
			
			
			return result;

			
		} // sendTo()
		
	} // OutgoingBehavior
	
	
	// This OutgoingListener class defines a threadable operation for sending messages from the component.
	//
	private class OutgoingListener implements Runnable {

		// This operation implements the run() operation defined by Runnable. It waits for a message to
		// be placed in the outgoing message queue and then sends it to the specified destination. The
		// behavior loops forever. This operation throws no exceptions.
		// 
		public void run() {
			
			while(true) {

				try {

					// Accept the next message from the queue.
					//
					Envelope e = outgoingMessages.take();
					
					
					// Connect to the destination component specified by the envelope and send 
					// the message.
					//
					Socket             socket = new Socket(e.getHost(), e.getPort());
					OutputStream       output = socket.getOutputStream();
					ObjectOutputStream out    = new ObjectOutputStream(output);
					out.writeObject(e);
					
					// Clean up by closing the streams and sockets.
					//
					out.close();
					output.close();
					socket.close();
				
				} catch (Exception e) {
				
					System.out.println(getClass().getName() + ".run():");
					System.out.println("   Exception occurred: " + e.toString());
				
				} // try 

			} // while
		
		} // run()
	
	} // OutgoingListener

	
	
	// This constructor initializes the component to listen for messages at the supplied endpoint and to
	// install the supplied behavior. If an error occurs while initializing the component, an error is logged
	// and the exception StartupException is thrown.
	// 
	public Component(Endpoint initEndpoint, Behavior initBehavior) throws InitializationException {
		
		try {
			
			// Save the supplied endpoint as the local endpoint and use it to establish a server socket on 
			// which the component will listen for incoming messages. 
			// 
			localEndpoint = initEndpoint;

			serverSocket  = new ServerSocket(localEndpoint.getPort());
			

			// Establish the outgoing behvaior.
			//
			outgoingBehavior = new OutgoingBehavior();

			
			// Save the supplied behavior and point it to the created outgoing behavior to handle sending
			// messages to other components.
			//
			behavior = initBehavior;
			behavior.setOutgoingBehavior(outgoingBehavior);
			
			
			// Establish the port listener on its own thread.
			//
			portListenerThread = new Thread(new PortListener());
			portListenerThread.start();
			
			
			// Establish the outgoing message queue and initialize the outgoing message listener.
			// 
			outgoingMessages = new LinkedBlockingQueue<Envelope>();
			outgoingListenerThread = new Thread(new OutgoingListener());
			outgoingListenerThread.start();
			
			
			// Start the behavior.
			//
			behavior.initialize();

		} catch (Exception e) {
			
			System.out.println(getClass().getName() + ".Component(Endpoint, Behavior):");
			System.out.println("   Exception occurred: " + e.toString());

			throw new InitializationException();
			
		} // try-catch
	
	} // constructor


	// This operation adds a message to the incoming message queue. There is no exception handling performed 
	// by this operation.
	//
	private void routeMessage(Envelope envelope) {

		behavior.addMessage(envelope);
	
	} // routeMessage()

	
} // Component